/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Negative Advanced Blend Equation Tests
 *//*--------------------------------------------------------------------*/

#include "es31fNegativeAdvancedBlendEquationTests.hpp"

#include "gluShaderProgram.hpp"
#include "glwEnums.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace NegativeTestShared
{
namespace
{

enum BlendEquation
{
	BLEND_EQUATION_MULTIPLY = 0,
	BLEND_EQUATION_SCREEN,
	BLEND_EQUATION_OVERLAY,
	BLEND_EQUATION_DARKEN,
	BLEND_EQUATION_LIGHTEN,
	BLEND_EQUATION_COLORDODGE,
	BLEND_EQUATION_COLORBURN,
	BLEND_EQUATION_HARDLIGHT,
	BLEND_EQUATION_SOFTLIGHT,
	BLEND_EQUATION_DIFFERENCE,
	BLEND_EQUATION_EXCLUSION,
	BLEND_EQUATION_HSL_HUE,
	BLEND_EQUATION_HSL_SATURATION,
	BLEND_EQUATION_HSL_COLOR,
	BLEND_EQUATION_HSL_LUMINOSITY,
	BLEND_EQUATION_ALL_EQUATIONS,

	BLEND_EQUATION_LAST
};

static const BlendEquation s_equations[] =
{
	BLEND_EQUATION_MULTIPLY,
	BLEND_EQUATION_SCREEN,
	BLEND_EQUATION_OVERLAY,
	BLEND_EQUATION_DARKEN,
	BLEND_EQUATION_LIGHTEN,
	BLEND_EQUATION_COLORDODGE,
	BLEND_EQUATION_COLORBURN,
	BLEND_EQUATION_HARDLIGHT,
	BLEND_EQUATION_SOFTLIGHT,
	BLEND_EQUATION_DIFFERENCE,
	BLEND_EQUATION_EXCLUSION,
	BLEND_EQUATION_HSL_HUE,
	BLEND_EQUATION_HSL_SATURATION,
	BLEND_EQUATION_HSL_COLOR,
	BLEND_EQUATION_HSL_LUMINOSITY
};

std::string getShaderLayoutEquation (BlendEquation equation)
{
	switch (equation)
	{
		case BLEND_EQUATION_MULTIPLY:          return "blend_support_multiply";
		case BLEND_EQUATION_SCREEN:            return "blend_support_screen";
		case BLEND_EQUATION_OVERLAY:           return "blend_support_overlay";
		case BLEND_EQUATION_DARKEN:            return "blend_support_darken";
		case BLEND_EQUATION_LIGHTEN:           return "blend_support_lighten";
		case BLEND_EQUATION_COLORDODGE:        return "blend_support_colordodge";
		case BLEND_EQUATION_COLORBURN:         return "blend_support_colorburn";
		case BLEND_EQUATION_HARDLIGHT:         return "blend_support_hardlight";
		case BLEND_EQUATION_SOFTLIGHT:         return "blend_support_softlight";
		case BLEND_EQUATION_DIFFERENCE:        return "blend_support_difference";
		case BLEND_EQUATION_EXCLUSION:         return "blend_support_exclusion";
		case BLEND_EQUATION_HSL_HUE:           return "blend_support_hsl_hue";
		case BLEND_EQUATION_HSL_SATURATION:    return "blend_support_hsl_saturation";
		case BLEND_EQUATION_HSL_COLOR:         return "blend_support_hsl_color";
		case BLEND_EQUATION_HSL_LUMINOSITY:    return "blend_support_hsl_luminosity";
		case BLEND_EQUATION_ALL_EQUATIONS:     return "blend_support_all_equations";
		default:
			DE_FATAL("Equation not supported.");
	}
	return DE_NULL;
}

glw::GLenum getEquation (BlendEquation equation)
{
	switch (equation)
	{
		case BLEND_EQUATION_MULTIPLY:          return GL_MULTIPLY;
		case BLEND_EQUATION_SCREEN:            return GL_SCREEN;
		case BLEND_EQUATION_OVERLAY:           return GL_OVERLAY;
		case BLEND_EQUATION_DARKEN:            return GL_DARKEN;
		case BLEND_EQUATION_LIGHTEN:           return GL_LIGHTEN;
		case BLEND_EQUATION_COLORDODGE:        return GL_COLORDODGE;
		case BLEND_EQUATION_COLORBURN:         return GL_COLORBURN;
		case BLEND_EQUATION_HARDLIGHT:         return GL_HARDLIGHT;
		case BLEND_EQUATION_SOFTLIGHT:         return GL_SOFTLIGHT;
		case BLEND_EQUATION_DIFFERENCE:        return GL_DIFFERENCE;
		case BLEND_EQUATION_EXCLUSION:         return GL_EXCLUSION;
		case BLEND_EQUATION_HSL_HUE:           return GL_HSL_HUE;
		case BLEND_EQUATION_HSL_SATURATION:    return GL_HSL_SATURATION;
		case BLEND_EQUATION_HSL_COLOR:         return GL_HSL_COLOR;
		case BLEND_EQUATION_HSL_LUMINOSITY:    return GL_HSL_LUMINOSITY;
		default:
			DE_FATAL("Equation not supported.");
	}
	return DE_NULL;
}

std::string generateVertexShaderSource (NegativeTestContext& ctx)
{
	const bool				supportsES32	= contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
	const glu::GLSLVersion	version	= supportsES32 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;
	std::ostringstream		source;

	source	<< glu::getGLSLVersionDeclaration(version) << "\n"
			<< "void main ()\n"
			<< "{\n"
			<< "	gl_Position = vec4(0.0);\n"
			<< "}\n";

	return source.str();
}

std::string generateFragmentShaderSource (NegativeTestContext& ctx, BlendEquation equation)
{
	const bool				supportsES32	= contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2));
	const glu::GLSLVersion	version	= supportsES32 ? glu::GLSL_VERSION_320_ES : glu::GLSL_VERSION_310_ES;
	std::ostringstream		source;

	source	<< glu::getGLSLVersionDeclaration(version) << "\n"
			<< (supportsES32 ? "" : "#extension GL_KHR_blend_equation_advanced : enable\n")
			<< "layout(" << getShaderLayoutEquation(equation) << ") out;\n"
			<< "layout(location=0) out mediump vec4 o_color;\n"
			<< "void main ()\n"
			<< "{\n"
			<< "	o_color = vec4(0);\n"
			<< "}\n";

	return source.str();
}

glu::ProgramSources generateProgramSources (NegativeTestContext& ctx, BlendEquation equation)
{
	return glu::ProgramSources()
		<< glu::VertexSource(generateVertexShaderSource(ctx))
		<< glu::FragmentSource(generateFragmentShaderSource(ctx, equation));
}

void blend_qualifier_mismatch (NegativeTestContext& ctx)
{
	TCU_CHECK_AND_THROW(NotSupportedError,
		ctx.isExtensionSupported("GL_KHR_blend_equation_advanced") || contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2)),
		"This test requires support for the extension GL_KHR_blend_equation_advanced or context version 3.2 or higher.");

	glw::GLuint vao = 0;
	bool isES = glu::isContextTypeES(ctx.getRenderContext().getType());
	if (!isES)
	{
		ctx.glGenVertexArrays(1, &vao);
		ctx.glBindVertexArray(vao);
	}

	ctx.beginSection("GL_INVALID_OPERATION is generated if blending is enabled, and the blend qualifier is different from blend_support_all_equations and does not match the blend equation.");
	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_equations); ++ndx)
	{
		glu::ShaderProgram program(ctx.getRenderContext(), generateProgramSources(ctx, s_equations[ndx]));

		ctx.getLog() << program;
		TCU_CHECK(program.isOk());

		ctx.glUseProgram(program.getProgram());
		ctx.expectError(GL_NO_ERROR);

		for (int ndx2 = 0; ndx2 < DE_LENGTH_OF_ARRAY(s_equations); ++ndx2)
		{
			if (s_equations[ndx] == s_equations[ndx2])
				continue;

			ctx.glEnable(GL_BLEND);
			ctx.glBlendEquation(getEquation(s_equations[ndx2]));
			ctx.expectError(GL_NO_ERROR);
			ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
			ctx.expectError(GL_INVALID_OPERATION);

			ctx.glDisable(GL_BLEND);
			ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
			ctx.expectError(GL_NO_ERROR);
		}
	}
	ctx.endSection();

	if (!isES)
		ctx.glDeleteVertexArrays(1, &vao);
}

void attachment_advanced_equation (NegativeTestContext& ctx)
{
	TCU_CHECK_AND_THROW(NotSupportedError,
		ctx.isExtensionSupported("GL_KHR_blend_equation_advanced") || contextSupports(ctx.getRenderContext().getType(), glu::ApiType::es(3, 2)),
		"This test requires support for the extension GL_KHR_blend_equation_advanced or context version 3.2 or higher.");

	glw::GLuint			vao				= 0;
	glw::GLuint			fbo				= 0x1234;
	glw::GLuint			texture			= 0x1234;
	const glw::GLenum	attachments[]	= { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
	const bool			isES			= glu::isContextTypeES(ctx.getRenderContext().getType());

	if (!isES)
	{
		ctx.glGenVertexArrays(1, &vao);
		ctx.glBindVertexArray(vao);
	}

	ctx.glGenTextures(1, &texture);
	ctx.glBindTexture(GL_TEXTURE_2D, texture);
	ctx.glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 32, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
	ctx.glGenFramebuffers(1, &fbo);
	ctx.glBindFramebuffer(GL_FRAMEBUFFER, fbo);
	ctx.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
	ctx.expectError(GL_NO_ERROR);
	ctx.glCheckFramebufferStatus(GL_FRAMEBUFFER);

	ctx.beginSection("GL_INVALID_OPERATION is generated if blending is enabled, advanced equations are used, and the draw buffer for other color outputs is not NONE unless NVX_blend_equation_advanced_multi_draw_buffers is supported.");
	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_equations); ++ndx)
	{
		glu::ShaderProgram	program(ctx.getRenderContext(), generateProgramSources(ctx, s_equations[ndx]));
		ctx.getLog() << program;
		TCU_CHECK(program.isOk());

		ctx.glUseProgram(program.getProgram());
		ctx.glEnable(GL_BLEND);
		ctx.glDrawBuffers(2, attachments);
		ctx.expectError(GL_NO_ERROR);
		ctx.glBlendEquation(getEquation(s_equations[ndx]));
		ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
		if (ctx.isExtensionSupported("GL_NVX_blend_equation_advanced_multi_draw_buffers"))
			ctx.expectError(GL_NO_ERROR);
		else
			ctx.expectError(GL_INVALID_OPERATION);
	}
	ctx.endSection();

	ctx.beginSection("GL_NO_ERROR is generated if no advanced blend equations are used.");
	ctx.glBlendEquation(GL_FUNC_ADD);
	ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
	ctx.expectError(GL_NO_ERROR);
	ctx.endSection();

	ctx.beginSection("GL_NO_ERROR is generated if blending is disabled.");
	ctx.glDisable(GL_BLEND);
	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_equations); ++ndx)
	{
		ctx.glBlendEquation(getEquation(s_equations[ndx]));
		ctx.glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_INT, 0);
		ctx.expectError(GL_NO_ERROR);
	}
	ctx.endSection();

	if (!isES)
		ctx.glDeleteVertexArrays(1, &vao);

	ctx.glDeleteFramebuffers(1, &fbo);
	ctx.glDeleteTextures(1, &texture);
}

} // anonymous

std::vector<FunctionContainer> getNegativeAdvancedBlendEquationTestFunctions (void)
{
	const FunctionContainer funcs[] =
	{
		{blend_qualifier_mismatch,			"blend_qualifier_mismatch",			"Test blend qualifier mismatch."			},
		{attachment_advanced_equation,		"attachment_advanced_equation",		"Test draw buffer for other color outputs." },
	};

	return std::vector<FunctionContainer>(DE_ARRAY_BEGIN(funcs), DE_ARRAY_END(funcs));
}

} // NegativeTestShared
} // Functional
} // gles31
} // deqp
